package com.zzz.friend.service.user.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zzz.common.core.constants.CacheConstants;
import com.zzz.common.core.constants.Constants;
import com.zzz.common.core.constants.HttpConstants;
import com.zzz.common.core.domain.LoginUser;
import com.zzz.common.core.enums.ResultCode;
import com.zzz.common.core.enums.UserIdentity;
import com.zzz.common.redis.service.RedisService;
import com.zzz.common.security.exception.ServiceException;
import com.zzz.common.security.service.TokenService;
import com.zzz.email.service.EmailService;
import com.zzz.friend.domain.user.User;
import com.zzz.friend.domain.user.dto.UseEmailLoginDTO;
import com.zzz.friend.domain.user.dto.UsePasswordDTO;
import com.zzz.friend.domain.user.dto.UserUpdateDTO;
import com.zzz.friend.domain.user.vo.UserLoginVO;
import com.zzz.friend.domain.user.vo.UserVO;
import com.zzz.friend.mapper.user.UserMapper;
import com.zzz.friend.service.user.UserService;
import com.zzz.friend.utils.BCryptUtils;
import jakarta.validation.constraints.Email;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

/**
 * @BelongsProject: oj-project
 * @BelongsPackage: com.zzz.friend.service.user.impl
 * @Author: zhai
 * @CreateTime: 2025-10-25  16:06
 * @Description: TODO
 * @Version: 1.0
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private EmailService emailService;

//    @Autowired
//    private AliSmsService aliSmsService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Value("${constant.code-expiration:5}")
    private Long codeExpiration;

    @Value("${constant.code-limit:3}")
    private Long sendLimit;

    @Value("${constant.is-send:false}")
    private boolean isSend;  //开关打开：true  开关关闭false

    @Value("${constant.code-quantity:6}")
    private Integer codeQuantity; // 表示生成验证码的个数

    @Value("${jwt.secret}")
    private String secret;

    /** 发送手机验证码
     * @description:
     * @author: zhai
     * @date: 2025/10/26 15:56
     * @param: [userDTO]
     * @return: int
     **/
//    @Override
//    public boolean sendPhoneCode(UserDTO userDTO) {
//        if (!validateContact(userDTO.getPhone(), "phone")) {
//            log.error("手机号格式错误");
//            throw new ServiceException(ResultCode.FAILED_PHONE_ERROR);
//        }
//        // 处理请求频繁,1分钟内禁止发送验证码
//        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
//        extracted(phoneCodeKey);
//        // 每天的验证码获取次数有一个限制  50次  第二天  计数清0 重新开始计数     计数  怎么存  存在哪
//        // 操作这个次数数据频繁、不需要存储、  记录的次数 有有效时间的（当天有效） redis  String  key：c:t:手机号
//        // 获取已经请求的次数和50进行比较     如果大于限制抛出异常。如果不大于限制，正常执行后续逻辑，并且将获取计数 + 1
//        String codeTimeKey = getTimeKey(userDTO.getPhone());
//        // 获取请求次数
//        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
//        if (sendTimes != null && sendTimes >= sendLimit) {
//            log.error("验证码当天发送达到上限了");
//            throw new ServiceException(ResultCode.FAILED_CODE_DAY_LIMIT);
//        }
//        String code = isSend ? aliSmsService.generateCode(codeQuantity) : Constants.DEFAULT_CODE;
//        log.info("生成的手机验证码是：{}", code);
//        // 存储验证码，并设置过期时间
//        redisService.setCacheObject(getPhoneCodeKey(userDTO.getPhone()), code, codeExpiration, TimeUnit.MINUTES);
//        if (isSend) {
//            boolean sendMobileCode = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
//            if (!sendMobileCode) {
//                log.error("发送验证码失败");
//                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
//            }
//        }
//        // increment增加次数，记录当天的验证码 code_time
//        redisService.increment(codeTimeKey);
//        // 如果发送次数为空，设置验证码时间键的过期时间为明天凌晨
//        updateExpire(sendTimes, codeTimeKey);
//        return true;
//    }

    /**
     * @description: 使用手机验证码登录/注册
     * @author: zhai
     * @date: 2025/10/27 12:55
     * @param: [userDTO]
     * @return: com.zzz.common.core.enums.ResultCode
     **/
//    @Override
//    public String usePhoneCodeLogin(String phone, String code) {
//        log.info("使用手机验证码登/注册，参数：{}, {}", phone, code);
//        // 1. 判断是否是老用户登录，如果是老用户登录直接校验一下身份信息即可，校验一下验证码就可以登录
//        checkCode(phone, code);
//        // 2. 如果是新用户：需要校验验证码，然后进行注册
//        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
//                .eq(User::getPhone, phone));
//        if (user == null) {
//            log.info("新用户注册，手机号：{}", phone);
//            user = new User();
//            user.setPhone(phone);
//            userMapper.insert(user);
//        }
//        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName());
//    }
    /**
     * @description: 发送邮箱验证码
     * @author: zhai
     * @date: 2025/10/26 15:56
     * @param: [registerDTO]
     * @return: int
     **/
    @Override
    public boolean sendEmailCode(UseEmailLoginDTO registerDTO) {
        // 验证格式
        if (!validateContact(registerDTO.getEmail(), "email")) {
            log.error("邮箱格式错误");
            throw new ServiceException(ResultCode.FAILED_EMAIL_ERROR);
        }
        // 1分钟内禁止发送验证码，从不redis中查找
        String emailCodeKey = getEmailCodeKey(registerDTO.getEmail());
        extracted(emailCodeKey);
        // 查看是否超过当天请求次数限制
        String codeTimeKey = getTimeKey(registerDTO.getEmail());
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        // 判断是否超过限制
        checkSendTimes(sendTimes);
        // 生成验证码：根据开关进行生成
        String code = isSend ? emailService.generateCode(codeQuantity) : Constants.DEFAULT_CODE;
        log.info("生成的邮箱验证码是：{}", code);
        redisService.setCacheObject(getEmailCodeKey(registerDTO.getEmail()), code, codeExpiration, TimeUnit.MINUTES);
        if (isSend) {
            boolean sendEmailCode = emailService.sendVerificationCodeWithTemplate(registerDTO.getEmail(), code);
            if (!sendEmailCode) {
                log.error("发送验证码失败");
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        redisService.increment(codeTimeKey);
        updateExpire(sendTimes, codeTimeKey);
        return true;
    }

    /**
     * @description: 使用邮箱验证码登录/注册
     * @author: zhai
     * @date: 2025/10/27 12:55
     * @param: [emailLoginDTO]
     * @return: com.zzz.common.core.enums.ResultCode
     **/
    @Override
    public String useEmailCodeLogin(UseEmailLoginDTO emailLoginDTO) {
        log.info("使用邮箱验证码登/注册，参数：{}", emailLoginDTO);
        checkEmailCode(emailLoginDTO.getEmail(), emailLoginDTO.getCode());
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getEmail, emailLoginDTO.getEmail()));
        if (user == null) {
            log.info("新用户注册，邮箱：{}", emailLoginDTO.getEmail());
            user = new User();
            user.setEmail(emailLoginDTO.getEmail());
            // 设置status字段为1（正常状态）
            user.setStatus(Constants.TRUE);
            userMapper.insert(user);
        }
        String token = tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(),
                user.getNickName(), user.getHeadImage());
        if (token == null) {
            log.error("token生成失败");
            throw new ServiceException(ResultCode.FAILED_TOKEN_ERROR);
        }
        return token;
    }

    /**
     * @description: 使用密码登录
     * @author: zhai
     * @date: 2025/10/27 12:55
     * @param: [passwordDTO]
     * @return: com.zzz.common.core.enums.ResultCode
     **/
    @Override
    public String usePasswordDTO(UsePasswordDTO passwordDTO) {
        log.info("使用手机验证码登/注册，参数：{}", passwordDTO);
        // 校验参数
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getEmail, passwordDTO.getEmail()));
        if (user == null) {
            log.error("用户不存在");
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (!BCryptUtils.matchesPassword(passwordDTO.getPassword(), user.getPassword())) {
            log.error("密码错误");
            throw new ServiceException(ResultCode.FAILED_PASSWORD_ERROR);
        }
        if (user.getStatus() == Constants.FALSE) {
            log.error("用户被禁用");
            throw new ServiceException(ResultCode.FAILED_LOCK_USER);
        }
        String token = tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(),
                user.getNickName(), user.getHeadImage());
        if (token == null) {
            log.error("token生成失败");
            throw new ServiceException(ResultCode.FAILED_TOKEN_ERROR);
        }
        log.info("用户登录成功");
        return token;
    }

    /**
     * @description: 登出
     * @author: zhai
     * @date: 2025/10/27 12:55
     * @param: [token]
     * @return: com.zzz.common.core.enums.ResultCode
     **/
    @Override
    public Boolean logout(String token) {
        log.info("用户登出：");
        // 检查token是否非空且以指定前缀开头，如果是则移除该前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            log.info("当前是以指定前缀开头：{}", token);
            token = token.replace(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token, secret);
    }

    /**
     * @description: 获取用户信息
     * @author: zhai
     * @date: 2025/10/31 19:43
     * @param: [token]
     * @return: com.zzz.friend.domain.user.vo.UserVO
     **/
    @Override
    public UserLoginVO info(String token) {
        log.info("开始获取用户信息");
        if (StrUtil.isEmpty(token)) {
            log.error("token为空");
            throw new ServiceException(ResultCode.FAILED_TOKEN_ERROR);
        }
        log.info("开始获取用户信息");
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        UserLoginVO UserLoginVO = new UserLoginVO();
        UserLoginVO.setNickName(loginUser.getNickName());
        UserLoginVO.setHeadImage(loginUser.getHeadImage());
        return UserLoginVO;
    }

    /**
     * @description: 获取用户详情
     * @author: zhai
     * @date: 2025/10/31 22:21
     * @param: []
     * @return: com.zzz.friend.domain.user.vo.UserVO
     **/
    @Override
    public UserVO detail() {
        return null;
    }

    /**
     * @description: 修改用户信息
     * @author: zhai
     * @date: 2025/10/31 22:21
     * @param: [userUpdateDTO]
     * @return: java.lang.Boolean
     **/
    @Override
    public Boolean editUserInfo(UserUpdateDTO userUpdateDTO) {
        log.info("修改用户信息，参数：{}", userUpdateDTO);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getUserId)
                .eq(User::getUserId, userUpdateDTO.getEmail()));
        log.info("用户信息：{}", user.getUserId());
        if (user == null) {
            log.error("用户不存在");
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        BeanUtils.copyProperties(userUpdateDTO, user);
        Integer flag = userMapper.updateById(user);
        if (flag <= 0 || flag == 0) {
            log.error("修改用户信息失败");
            throw new ServiceException(ResultCode.FAILED_USER_INFO_ERROR);
        }
        return true;
    }

    /**
     * @description: 修改用户头像
     * @author: zhai
     * @date: 2025/10/31 22:21
     * @param: [headImage]
     * @return: java.lang.Boolean
     **/
    @Override
    public Boolean updateHeadImage(String headImage) {
        return null;
    }

    /**
     * 更新验证码过期时间
     **/
    private void updateExpire(Long sendTimes, String codeTimeKey) {
        if (sendTimes == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
    }

    /**
     * 检查发送次数
     **/
    private void checkSendTimes(Long sendTimes) {
        if (sendTimes != null && sendTimes >= sendLimit) {
            log.error("验证码当天发送达到上限了");
            throw new ServiceException(ResultCode.FAILED_CODE_DAY_LIMIT);
        }
    }

    /**
     * 检查验证码是否可以再次请求
     **/
    private void extracted(String emailCodeKey) {
        Long expire = redisService.getExpire(emailCodeKey, TimeUnit.SECONDS);
        if (expire != null && (codeExpiration * 60 - expire) < 60) {
            log.error("请勿频繁发送验证码");
            throw new ServiceException(ResultCode.FAILED_CODE_LIMIT);
        }
    }

    /**
     * 获取邮箱验证码缓存键
     * @param email 邮箱
     * @return 缓存键
     */
    private String getEmailCodeKey(String email) {
        return CacheConstants.EMAIL_CODE_KEY + email;
    }

    /**
     * 统一校验联系方式（邮箱或手机号）
     * @param contact 联系方式
     * @param type 类型：email或phone
     * @return 是否校验通过
     */
    private boolean validateContact(String contact, String type) {
        if (contact == null || contact.trim().isEmpty()) {
            return false;
        }
        
        switch (type.toLowerCase()) {
            case "email":
                String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
                return contact.matches(emailRegex);
            case "phone":
                String phoneRegex = "^1[3-9]\\d{8}$";
                return contact.matches(phoneRegex);
            default:
                return false;
        }
    }

    /**
     * 构建手机验证码缓存键
     * @param phone 手机号
     * @return 缓存键
     */
    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    /**
     * 构建手机验证码缓存键
     * @param phone 手机号
     * @return 缓存键
     */
    private String getTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }

    /**
     * 校验验证码
     **/
    private void checkCode(String phone, String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        String phoneCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (StrUtil.isEmpty(phoneCode)) {
            log.error("验证码已过期");
            throw new ServiceException(ResultCode.FAILED_CODE_EXPIRE);
        }
        if (!phoneCode.equals(code)) {
            log.error("验证码错误");
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR);
        }
        redisService.deleteObject(phoneCodeKey);
    }
    /**
     * 校验邮箱验证码
     **/
    private void checkEmailCode(@Email(message = "邮箱格式错误") String email, String code) {
        String emailCodeKey = getEmailCodeKey(email);
        String emailCode = redisService.getCacheObject(emailCodeKey, String.class);
        if (StrUtil.isEmpty(emailCode)) {
            log.error("邮箱验证码已过期");
            throw new ServiceException(ResultCode.FAILED_CODE_EXPIRE);
        }
        if (!emailCode.equals(code)) {
            log.error("邮箱验证码错误");
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR);
        }
        redisService.deleteObject(emailCodeKey);
    }
}
